<?php
/**
 * This is interface for storage via ftp protocol
 * @package     nuo
 * @subpackage  nuo.storage
 * @author      Nam Nguyen <nam.ngvan@gmail.com>
 * @version     1.0
 * @since       Apr 19, 2013
 */
class Nuo_Storage_Ftp extends Nuo_Storage_Abstract
{
	/**
	 * @var resource
	 */
	protected $_ftpResource = NULL;

	/**
	 * @var string
	 */
	protected $_host = 'localhost';

	/**
	 * @var int
	 */
	protected $_port = 21;

	/**
	 * @var string
	 */
	protected $_username = '';

	/**
	 * @var string
	 */
	protected $_password = '';

	/**
	 * @var string
	 */
	protected $_timeout = 20;

	/**
	 * @var bool
	 */
	protected $_isPasv = TRUE;

	/**
	 * @var bool
	 */
	protected $_isSSL = FALSE;

	/**
	 * constructor
	 * @param array $config
	 */
	function __construct($config)
	{
		extract($config, EXTR_SKIP);

		/**
		 * require value
		 */
		$this -> setId($id);
		$this -> setHost($host);
		$this -> setUsername($username);
		$this -> setPassword($password);
		$this -> setBasePath($basePath);
		$this -> setBaseUrl($baseUrl);

		/**
		 * optional value
		 */
		$this -> setIsSSL($ssl);

		/**
		 * always use pasv mode because connection timeout problem.
		 */
		$this -> setPort($port);
		$this -> setTimeout($timeout);
	}

	/**
	 * set host key
	 * @param string $host IP or domain name
	 * @return Storage_Ftp
	 */
	public function setHost($host)
	{
		$this->_host = (string)$host;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getHost()
	{
		return $this -> _host;
	}

	/**
	 * set port
	 * @param int $port
	 * @return Storage_Ftp
	 */
	public function setPort($port)
	{
		$port = (int)$port;

		if (!$port)
		{
			$port = 22;
		}

		return $this;
	}

	/**
	 * @return int
	 */
	public function getPort()
	{
		return $this -> _port;
	}

	/**
	 * @param string $username
	 * @return Storage_Ftp
	 */
	public function setUsername($username)
	{
		$this -> _username = $username;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getUsername()
	{
		return $this -> _username;
	}

	/**
	 * @param string $password
	 * @return Storage_Ftp
	 */
	public function setPassword($password)
	{
		$this -> _password = $password;
		return $this;
	}

	/**
	 * @return string
	 */
	public function getPassword()
	{
		return $this -> _password;
	}

	/**
	 * @param int $timeout in seconds
	 * @return Storage_Ftp
	 */
	public function setTimeout($timeout)
	{
		$timeout = (int)$timeout;

		if ($timeout < 20)
		{
			$timeout = 20;
		}

		return $this;
	}

	/**
	 * @param int $timeout in seconds
	 * @return Storage_Ssh2
	 */
	public function getTimeout()
	{
		return $this -> _timeout;
	}

	/**
	 * @param bool $flag
	 * @return Storage_Ftp
	 */
	public function setIsSSL($flag)
	{
		$this -> _isSSL = (bool)$flag;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function getIsSSL()
	{
		return $this -> _isSSL;
	}

	/**
	 * @param bool $flag
	 * @return Storage_Ftp
	 */
	public function setPasv($flag)
	{
		$this -> _isPasv = (bool)$flg;
		return $this;
	}

	/**
	 * @return bool
	 */
	public function getIsPasv()
	{
		return $this -> _isPasv;
	}

	/**
	 * connect ftp resource handle
	 * @return void
	 */
	function connect()
	{
		if ($this -> _ftpResource)
		{
			return;
		}

		if ($this -> _isSSL)
		{
			$this -> _ftpResource = @ftp_ssl_connect($this -> _host, $this -> _port, $this -> _timeout);
		}
		else
		{
			$this -> _ftpResource = @ftp_connect($this -> _host, $this -> _port, $this -> _timeout);

		}

		if (NULL == $this -> _ftpResource)
		{
			throw new Exception(strtr('could not connect to {host}:{port}', array(
				'{host}' => $this -> _host,
				'{port}' => $this -> _port
			)));
		}

		if (!@ftp_login($this -> _ftpResource, $this -> _username, $this -> _password) or !ftp_pasv($this -> _ftpResource, $this -> _isPasv))
		{
			throw new Exception(strtr('could not authenticate to {host}:{port}', array(
				'{host}' => $this -> _host,
				'{port}' => $this -> _port,
			)));
		}
	}

	/**
	 * disconnect session
	 * @return Storage_Ftp
	 */
	function disconnect()
	{
		if ($this -> _ftpResource and is_resource($this -> _ftpResource))
		{
			@ftp_close($this -> _ftpResource);
			$this -> _ftpResource = NULL;
		}
		return $this;
	}

	/**
	 * @return resource
	 */
	function getFtpResource()
	{
		$this -> _ftpResource or $this -> connect();

		return $this -> _ftpResource;
	}

	/**
	 * @param string $command
	 * @return bool
	 * @throws Exception
	 */
	public function command($command)
	{
		if (!@ftp_site($this -> getFtpResource(), $command))
		{
			throw new Exception(strtr('Unable to execute SITE "{cmd}"', array('{cmd}' => $command)));
		}
		return TRUE;
	}

	/**
	 * copy content of relative path to local file and set mode
	 * @param string $path
	 * @param string $local absolute file path
	 * @param int mode
	 * @return bool
	 * @throws Exception
	 */
	public function get($path, $local)
	{
		self::_makeLocalDir(dirname($local));

		$remote = $this -> getPath($path);

		$ftpResource = $this -> getFtpResource();

		// Non-blocking mode
		if (@function_exists('ftp_nb_get'))
		{
			$res = @ftp_nb_get($ftpResource, $local, $remote, FTP_BINARY);
			while ($res == FTP_MOREDATA)
			{
				$res = @ftp_nb_continue($ftpResource);
			}
			$return  ($res === FTP_FINISHED);
		}
		// Blocking mode
		else
		{
			$return  = @ftp_get($ftpResource, $local, $remote, FTP_BINARY);
		}

		// Error
		if ($return == FALSE)
		{
			throw new Exception(strtr('Unable to get "{from}" to "{to}"', array(
				'{from}' => $remote,
				'{to}' => $local
			)));
		}

		return TRUE;
	}

	/**
	 * @param string $path relative path
	 * @param string $local  absolute local file path
	 * @return true|false
	 * @throws Exception
	 */
	function put($path, $local)
	{

		if (!is_file($local))
		{
			throw new Exception(strtr('file "{file}" does not exsits ', array('{file}' => $local)));
		}

		// try to make dir to resolve issue with no directory exists.
		if ($this -> makeDir(dirname($path)))
		{
			// try to make dir before put data or content.
		}

		$remote = $this -> getPath($path);

		$ftpResource = $this -> getFtpResource();

		if (function_exists('ftp_nb_put'))
		{

			$res = @ftp_nb_put($ftpResource, $remote, $local, FTP_BINARY);

			while ($res == FTP_MOREDATA)
			{
				$res = @ftp_nb_continue($ftpResource);
			}

			if (FTP_FINISHED !== $res)
			{
				throw new Exception(strtr('could not put "{from}" to "{to}"', array(
					'{from}' => $local,
					'{to}' => $remote,
				)));
			}
		}
		else
		if (!@ftp_put($ftpResource, $remote, $local, FTP_BINARY))
		{
			throw new Exception(strtr('could not put "{from}" to "{to}"', array(
				'{from}' => $local,
				'{to}' => $remote,
			)));
		}

		/**
		 * try to set mode
		 */
		@ftp_chmod($ftpResource, $mode = 0644, $remote);

		return TRUE;
	}

	/**
	 * @link http://www.php.net/manual/en/function.ssh2-sftp-unlink.php
	 * delete relative path
	 * @param string $path
	 * @return bool
	 * @throws Exception
	 */
	function delete($path)
	{
		$remote = $this -> getPath($path);

		if (!@ftp_delete($this -> getFtpResource(), $remote))
		{
			throw new Exception(strtr('could not remove "{from}"', array('{from}' => $remote, )));
		}
		return TRUE;
	}

	/**
	 * @todo need to implement
	 * @param string $path relative path in database
	 * @return string|FALSE
	 * @throws Exception
	 */
	function getContents($path)
	{

	}

	/**
	 * @param string $path relative path in database
	 * @param string $data
	 * @return string
	 * @throws Exception
	 */
	function putContents($path, $data)
	{

	}

	/**
	 * @param string $path
	 * @return bool
	 */
	public function isFile($path)
	{
		return @ftp_mdtm($this -> getFtpResource(), $this -> getPath($path));
	}

	/**
	 * @param string $path
	 * @return bool
	 */
	public function isDir($path)
	{
		$remote = $this -> getPath($path);

		$ftpResource = $this -> getFtpResource();

		if (!$pwd = @ftp_pwd($ftpResource))
		{
			$pwd = '/';
		}

		$isDir = @ftp_chdir($ftpResource, $remote);

		if ($isDir)
		{
			@ftp_chdir($ftpResource, $pwd);
		}

		return $isDir;
	}

	/**
	 * @link http://www.php.net/manual/en/function.ssh2-sftp-mkdir.php
	 * @param string $path
	 * @param $mode default  = 755
	 * @return bool
	 * @throws Exception
	 */
	function makeDir($path, $mode = 0755)
	{

		// Check if the directory already exists
		if ($this -> isDir($path))
		{
			return TRUE;
		}

		$ftpResource = $this -> getFtpResource();

		$remote = $this -> getPath($path);

		$parts = array_filter(explode('/', $remote));

		$pPath = '/' . array_shift($parts);

		while (count($parts) > 0)
		{
			$pPath .= '/' . array_shift($parts);

			if (strpos($this -> _basePath, $pPath) === 0)
			{
				continue;
			}

			// If it doesn't exist, create it
			if (FALSE === ftp_chdir($ftpResource, $pPath))
			{
				if (!@ftp_mkdir($ftpResource, $pPath))
				{
					throw new Exception(strtr('Unable to make directory "{dir}"', array('{dir}' => $pPath)));
				}
			}
		}

		if (!@ftp_chdir($ftpResource, $remote))
		{
			throw new Exception(strtr('Unable to make directory "{dir}"', array('{dir}' => $remote)));
		}
		return TRUE;
	}

	/**
	 * @link http://www.php.net/manual/en/function.ssh2-sftp-rmdir.php
	 * @param string $path
	 * @return bool
	 * @throws Exception
	 */
	function removeDir($path)
	{
		$dir = $this -> getPath($path);

		if (!@ftp_rmdir($this -> getFtpResource(), $dir))
		{
			throw new Exception(strtr('could not remove "{dir}"', array('{dir}' => $dir)));
		}
		return TRUE;
	}

}
